package com.timk.goserver.client.ui.screens;

import java.util.List;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupListener;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.timk.goserver.client.board.Board;
import com.timk.goserver.client.board.BoardController;
import com.timk.goserver.client.board.BoardListener;
import com.timk.goserver.client.model.ClientGameInfo;
import com.timk.goserver.client.model.ClientUserInfo;
import com.timk.goserver.client.services.GameService;
import com.timk.goserver.client.services.NoSessionException;
import com.timk.goserver.client.ui.widgets.CenteredDialog;
import com.timk.goserver.client.ui.widgets.ConfirmDialog;
import com.timk.goserver.client.ui.widgets.NoFocusListener;
import com.timk.goserver.client.ui.widgets.StatusLabel;

/**
 * Panel for displaying the game and playing moves.
 * @author TKington
 *
 */
public class GameplayDialog extends CenteredDialog // NOPMD - Too many fields
							implements BoardPanelListener, BoardListener {
	BoardController controller; // NOPMD - Explicit scoping
	private final BoardPanel boardPanel;
	private final VerticalPanel rightPanel;
	List games; // NOPMD - Explicit scoping
	ClientGameInfo curGame; // NOPMD - Explicit scoping
	int curGameIndex; // NOPMD - Explicit scoping
	final StatusLabel status; // NOPMD - Explicit scoping
	final Image loadingImage; // NOPMD - Explicit scoping
	
	private final Button submitButton;
	private final Button skipButton;
	private final Button resignButton;
	private final Button passButton;
	private final Button resumeButton;
	private final Button closeButton;
	
	private final HTML moveInfo;
	private final HTML blackInfo;
	private final HTML whiteInfo;
	private final HTML ruleInfo;
	
	final TextArea editCommentArea;	// NOPMD - Explicit scoping
	final TextArea noteArea;	// NOPMD - Explicit scoping
	final TextArea showCommentArea;	// NOPMD - Explicit scoping
	
	private boolean allowPlay;
	
	/**
	 * Creates a GameplayDialog.
	 * @param parent Panel to center on
	 */
	public GameplayDialog(UIObject parent) { // NOPMD - Complexity
		super(parent);
		
		boardPanel = new BoardPanel();
		controller = new BoardController();
		boardPanel.addListener(controller);
		boardPanel.addListener(this);

		VerticalPanel vertPanel = new VerticalPanel();
		vertPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		
		HorizontalPanel navButtonPanel = new HorizontalPanel();
		
		Button beginButton = new Button("&lt;&lt;");
		beginButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				controller.moveToBeginning();
				showButtons();
				showComment();
			}
		});
		navButtonPanel.add(beginButton);

		Button backButton = new Button("&lt;-");
		backButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				controller.prev(true);
				showButtons();
				showComment();
			}
		});
		navButtonPanel.add(backButton);

		Button homeButton = new Button("O");
		homeButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				controller.moveToCurpos();
				showButtons();
				showComment();
			}
		});
		navButtonPanel.add(homeButton);

		Button fwdButton = new Button("-&gt;");
		fwdButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				controller.next(true);
				showButtons();
				showComment();
			}
		});
		navButtonPanel.add(fwdButton);

		Button endButton = new Button("&gt;&gt;");
		endButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				controller.moveToEnd();
				showButtons();
				showComment();
			}
		});
		navButtonPanel.add(endButton);

		status = new StatusLabel();
		
		HorizontalPanel submitButtonPanel = new HorizontalPanel();
		
		submitButton = new Button("Submit");
		submitButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				submitMoves();
			}
		});
		submitButtonPanel.add(submitButton);

		skipButton = new Button("Next Game");
		skipButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				nextGame();
			}
		});
		submitButtonPanel.add(skipButton);
		
		passButton = new Button("Pass");
		passButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				pass();
			}
		});
		submitButtonPanel.add(passButton);
		
		resumeButton = new Button("Resume Game");
		resumeButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				resumeGame();
				showButtons();
			}
		});
		submitButtonPanel.add(resumeButton);
		
		resignButton = new Button("Resign");
		resignButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				confirmResign();
			}
		});
		submitButtonPanel.add(resignButton);

		closeButton = new Button("Close");
		closeButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				onClose();
			}
		});
		submitButtonPanel.add(closeButton);
		
		moveInfo = new HTML();
		blackInfo = new HTML();
		whiteInfo = new HTML();
		ruleInfo = new HTML();
		
		DockPanel boardAndCommentsPanel = new DockPanel();
		boardAndCommentsPanel.add(boardPanel, DockPanel.CENTER);
		
		rightPanel = new VerticalPanel();
		rightPanel.add(new Label("Message"));
		editCommentArea = new TextArea();
		rightPanel.add(editCommentArea);
		editCommentArea.setHeight("100px");
		rightPanel.add(new Label("Private Notes:"));
		noteArea = new TextArea();
		rightPanel.add(noteArea);
		noteArea.setHeight("200px");
		
		boardAndCommentsPanel.add(rightPanel, DockPanel.EAST);
		rightPanel.setHeight("100%");
		boardAndCommentsPanel.setCellHeight(rightPanel, "100%");
		
		showCommentArea = new TextArea();
		showCommentArea.setWidth("100%");
		showCommentArea.setHeight("100px");
		boardAndCommentsPanel.add(showCommentArea, DockPanel.SOUTH);
		boardAndCommentsPanel.setCellWidth(showCommentArea, "100%");
		
		showCommentArea.addFocusListener(
				new NoFocusListener(showCommentArea));

		loadingImage = new Image("images/whiteloading.gif");
		vertPanel.add(loadingImage);
		
		vertPanel.add(navButtonPanel);
		vertPanel.add(boardAndCommentsPanel);
		vertPanel.add(status);
		vertPanel.add(submitButtonPanel);
		vertPanel.add(moveInfo);
		vertPanel.add(blackInfo);
		vertPanel.add(whiteInfo);
		vertPanel.add(ruleInfo);
		
		setWidget(vertPanel);
	}
	
	void onClose() {
		enableButtons(false);
		loadingImage.setVisible(true);
		
		saveNotes(new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				status.clear();
				hide();
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error saving notes: " + caught);
			}
		});
	}
	
	/**
	 * Shows a set of games
	 * @param gameList List&lt;ClientGameInfo&gt;
	 * @param startingIndex index of first game to show
	 * @param canPlay true if the user is allowed to submit moves
	 */
	public void showGames(List gameList, int startingIndex, boolean canPlay) {
		allowPlay = canPlay;
		games = gameList;
		show();
		
		curGameIndex = startingIndex;
		curGame = (ClientGameInfo)games.get(startingIndex);
		loadCurGameFromServer(false);
	}
	
	/**
	 * Loads a game and displays it on the board
	 * @param g
	 */
	void loadGame(int gameIndex) {
		curGame = (ClientGameInfo)games.get(gameIndex);
		curGameIndex = gameIndex;
		
		displayGameInfo();
		if(findCurUser(curGame) == -1) {
			rightPanel.setVisible(false);
		} else {
			rightPanel.setVisible(true);
			noteArea.setText(curGame.getNotes());
			editCommentArea.setText("");
		}
		
		Board board = controller.loadGame(curGame);
		
		if(allowPlay) {
			String comments = controller.getCommentsSince(
					curGame.getLastInteractiveMove());
			showCommentArea.setText(comments);
		} else {
			showCommentArea.setText(controller.getComment());
		}
		
		showButtons();
		
		board.addListener(boardPanel);
		board.addListener(this);
		board.fireRepaint();
	}
	
	private int findCurUser(ClientGameInfo game) {
		ClientUserInfo [] players = game.getPlayers();
		for(int i = 0; i < players.length; i++) {
			if(players[i].getUsername().equals(
					MainPanel.loggedInUser.getUsername())) {
				return i;
			}
		}
		
		return -1;
	}
	
	private void displayGameInfo() {
		StringBuffer blackNames = new StringBuffer();
		StringBuffer whiteNames = new StringBuffer();
		
		curGame.getBlackNames(blackNames);
		curGame.getWhiteNames(whiteNames);
		
		setText(blackNames + " vs. " + whiteNames);
		
		StringBuffer info = new StringBuffer(20);
		int moveNumber = controller.getMoveNumber();
		
		if(moveNumber > 0) {
			info.append("<b>Move</b> ").append(controller.getMoveNumber());
		
			ClientUserInfo [] players = curGame.getPlayers();
			int playerNum = controller.getMoveNumber() - 1;
			
			if(curGame.getHandicap() >= 2) {
				playerNum = Math.max(0, playerNum - curGame.getHandicap() + 1);
			}
			
			playerNum %= players.length;
			
			String playerName = players[playerNum].getUsername();
			info.append(" (" + playerName + ")");
		}
		
		moveInfo.setHTML(info.toString());
		
		info = new StringBuffer(50);
		info.append("<b>Black:</b> ").append(blackNames).append(" <b>Prisoners:</b> ")
				.append(controller.getNumCapturedByBlack());
		
		if(controller.isScoring()) {
			info.append(" <b>Score:</b> ").append(controller.getBlackScore());
		}
		
		blackInfo.setHTML(info.toString());

		info = new StringBuffer(50);
		info.append("<b>White:</b> ").append(whiteNames).append(" <b>Prisoners:</b> ")
				.append(controller.getNumCapturedByWhite());
		
		if(controller.isScoring()) {
			double score = controller.getWhiteScore() + curGame.getKomi();
			info.append(" <b>Score:</b> ").append(score);
		}
		
		whiteInfo.setHTML(info.toString());
		
		ruleInfo.setHTML("<b>Handicap:</b> " + curGame.getHandicap() +
				" <b>Komi:</b> " + curGame.getKomi());
	}
	
	void nextGame() {
		enableButtons(false);
		loadingImage.setVisible(true);
		
		saveNotes(new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				status.clear();
				
				curGameIndex++;
				if(curGameIndex < games.size()) {
					curGame = (ClientGameInfo)games.get(curGameIndex);
					loadCurGameFromServer(false);
				} else {
					hide();
				}	
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error saving notes");
			}
		});
	}
	
	void pass() {
		if(controller.lastMoveWasPass()) {
			controller.pass();
			showButtons();
		}
		else {
			ConfirmDialog dlg = new ConfirmDialog(this,
					"Confirm pass", "Really pass?");
			dlg.addPopupListener(new PopupListener() {
				public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
					if(((ConfirmDialog)sender).getResult() == ConfirmDialog.CHOICE_NO) {
						return;
					}
					
					controller.pass();
					submitMoves();
				}
			});
			
			dlg.show();
		}
	}
	
	public void boardChanged(Board board) {
		displayGameInfo();
	}

	public void onClick(int x, int y) {
		showButtons();
	}
	
	void showComment() {
		showCommentArea.setText(controller.getComment());
	}

	void showButtons() {
		skipButton.setVisible(curGameIndex < games.size() - 1);
		resignButton.setVisible(allowPlay && !controller.isScoring());
		
		passButton.setVisible(allowPlay && !controller.isScoring());
		resumeButton.setVisible(allowPlay && controller.isScoring()
				&& !controller.hasPlayedNewMoves());
		submitButton.setVisible(allowPlay);
	}
	
	void confirmDeleteGame() {
		ConfirmDialog dlg = new ConfirmDialog(this,
				"Confirm delete", "Really delete this game?");
		dlg.addPopupListener(new PopupListener() {
			public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
				if(((ConfirmDialog)sender).getResult() == ConfirmDialog.CHOICE_NO) {
					return;
				}
				
				deleteGame();
			}
		});
		
		dlg.show();
	}
	
	void deleteGame() {
		enableButtons(false);
		loadingImage.setVisible(true);
		
		MainPanel.gameService.deleteGame(curGame.getGameId(), new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				
				int success = ((Integer)result).intValue();
				switch(success) {
				case GameService.STATUS_NO_SUCH_GAME:
					status.setError("Error: No such game");
					break;
				case GameService.STATUS_SUCCESS:
					status.clear();
					nextGame();
					break;
				default:
					status.setError("Unknown error: " + success);
					break;
				}
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error deleting game: " + caught.getMessage());
			}
		});
	}
	
	void confirmResign() {
		ConfirmDialog dlg = new ConfirmDialog(this,
				"Confirm resignation", "Really resign?");
		dlg.addPopupListener(new PopupListener() {
			public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
				if(((ConfirmDialog)sender).getResult() == ConfirmDialog.CHOICE_NO) {
					return;
				}
				
				resign();
			}
		});
		
		dlg.show();
	}
	
	void resign() {
		String comment = getComment();
		if(comment == null) {
			return;
		}
		
		enableButtons(false);
		loadingImage.setVisible(true);
		
		MainPanel.gameService.resignGame(curGame.getGameId(), comment,
				new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				
				int success = ((Integer)result).intValue();
				switch(success) {
				case GameService.STATUS_NO_SUCH_GAME:
					status.setError("No such game");
					break;
				case GameService.STATUS_ILLEGAL_STATE_TRANSITION:
					status.setError("Game is not running");
					break;
				case GameService.STATUS_WRONG_USER:
					status.setError("It's not your turn");
					break;
				case GameService.STATUS_SUCCESS:
					status.clear();
					nextGame();
					break;
				default:
					status.setError("Unknown error: " + success);
					break;
				}
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error resigning game: " + caught.getMessage());
			}
		});
	}
	
	void saveNotes(AsyncCallback callback) {
		String notes = noteArea.getText();
		if(notes.equals(curGame.getNotes())) {
			callback.onSuccess(new Integer(GameService.STATUS_SUCCESS));
			
			return;
		}
		
		curGame.setNotes(notes);
		MainPanel.gameService.setNotes(curGame.getGameId(), notes, callback);
	}
	
	void submitMoves() {
		String comment = getComment();
		if(comment == null) {
			return;
		}
		
		if(controller.isScoring()) {
			submitDeadStones(comment);
			return;
		}
		
		if(!controller.hasPlayedNewMoves()) {
			return;
		}
		
		int numOldMoves = controller.getNumFinalMoves();
		List moves = controller.getNewMoves();
		if(moves == null) {
			status.setError("No new moves to submit");
			return;
		}

		enableButtons(false);
		loadingImage.setVisible(true);
		MainPanel.gameService.playMoves(curGame.getGameId(), numOldMoves + 1,
				moves, comment, new SubmitMovesCallback());
	}
	
	private String checkComment(String comment) {
		int index = comment.indexOf('[');
		if(index != -1) {
			return "[ is illegal in comments";
		}
		
		index = comment.indexOf(']');
		if(index != -1) {
			return "] is illegal in comments";
		}
		
		index = comment.indexOf("<h>");
		int index2 = comment.indexOf("</h>");
		
		if(index == -1) {
			if(index2 == -1) {
				return null;
			}
			
			return "Unmatched </h> tag";
		}
		
		if(index2 == -1) {
			return "Unmatched <h> tag";
		}
		
		if(index > index2) {
			return "Unmatched </h> tag";
		}
		
		if(comment.indexOf("<h>", index + 1) != -1) {
			return "Only one <h> tag allowed";
		}
		
		if(comment.indexOf("</h>", index2 + 1) != -1) {
			return "Only one </h> tag allowed";
		}
		
		return null;
	}
	
	private String getComment() {
		String comment = editCommentArea.getText();
		String error = checkComment(comment);
		if(error != null) {
			status.setError(error);
			return null;
		}
		
		return comment;
	}
	
	class SubmitMovesCallback implements AsyncCallback {
		public void onSuccess(Object result) {
			loadingImage.setVisible(false);
			enableButtons(true);
			
			int err = ((Integer)result).intValue();
			switch(err) {
			case GameService.STATUS_SUCCESS:
				status.clear();
				nextGame();
				break;
			case GameService.STATUS_AUTO_REPLY:
				status.clear();
				loadCurGameFromServer(true);
				break;
			case GameService.STATUS_WRONG_MOVE_NUMBER:
				status.setError("Sent an invalid move number");
				break;
			case GameService.STATUS_WRONG_USER:
				status.setError("You are not the right user to play this move");
				break;
			case GameService.STATUS_NO_SUCH_GAME:
				status.setError("That game doesn't exist");
				break;
			case GameService.STATUS_ILLEGAL_MOVE:
				status.setError("That's an illegal move");
				break;
			default:
				status.setError("Unknown error: " + err);
				break;
			}
		}
		
		public void onFailure(Throwable caught) {
			loadingImage.setVisible(false);
			
			if(caught instanceof NoSessionException) {
				MainPanel.sessionExpired(caught.getMessage());
				return;
			}
			
			enableButtons(true);
			status.setError("Could not send move(s): " + caught.getMessage());
		}
	}
	
	private void submitDeadStones(String comment) {
		enableButtons(false);
		loadingImage.setVisible(true);
		
		MainPanel.gameService.submitDeadStones(curGame.getGameId(),
				controller.getDeadStones(), comment, new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				
				int err = ((Integer)result).intValue();
				switch(err) {
				case GameService.STATUS_SUCCESS:
					status.clear();
					nextGame();
					break;
				case GameService.STATUS_WRONG_USER:
					status.setError("You are not the right user to play this move");
					break;
				case GameService.STATUS_NO_SUCH_GAME:
					status.setError("That game doesn't exist");
					break;
				default:
					status.setError("Unknown error: " + err);
					break;
				}
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Could not send dead stone(s): " + caught.getMessage());
			}
		});
	}
	
	void resumeGame() {
		enableButtons(false);
		loadingImage.setVisible(true);
		
		MainPanel.gameService.resumeGame(curGame.getGameId(), new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				
				int err = ((Integer)result).intValue();
				switch(err) {
				case GameService.STATUS_SUCCESS:
					status.clear();
					loadCurGameFromServer(true);
					break;
				case GameService.STATUS_WRONG_USER:
					status.setError("You are not the right user to play this move");
					break;
				case GameService.STATUS_NO_SUCH_GAME:
					status.setError("That game doesn't exist");
					break;
				default:
					status.setError("Unknown error: " + err);
					break;
				}
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error resuming game: " + caught.getMessage());
			}
		});
	}
	
	void loadCurGameFromServer(boolean preserveNotes) {
		enableButtons(false);
		loadingImage.setVisible(true);
		final boolean preserve = preserveNotes;
		final String notes = noteArea.getText();
		
		MainPanel.gameService.getGame(curGame.getGameId(), new AsyncCallback() {
			public void onSuccess(Object result) {
				loadingImage.setVisible(false);
				enableButtons(true);
				
				if(result == null) {
					status.setError("Couldn't load game");
				} else {
					status.clear();
					games.set(curGameIndex, result);
					loadGame(curGameIndex);
					
					if(preserve) {
						noteArea.setText(notes);
					}
				}
			}
			
			public void onFailure(Throwable caught) {
				loadingImage.setVisible(false);
				
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error loading game: " + caught.getMessage());
			}
		});
	}
	
	void enableButtons(boolean enabled) {
		submitButton.setEnabled(enabled);
		skipButton.setEnabled(enabled);
		resignButton.setEnabled(enabled);
		passButton.setEnabled(enabled);
		resumeButton.setEnabled(enabled);
		closeButton.setEnabled(enabled);
	}
}
